package com.dlc.shop.service.impl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dlc.shop.bean.app.dto.ProdCommDataDto;
import com.dlc.shop.bean.app.dto.ProdCommDto;
import com.dlc.shop.bean.app.param.ProdCommParam;
import com.dlc.shop.bean.enums.EsOperationType;
import com.dlc.shop.bean.enums.ProdStatusEnums;
import com.dlc.shop.bean.event.EsProductUpdateEvent;
import com.dlc.shop.bean.model.*;
import com.dlc.shop.bean.vo.ShopDetailVO;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.constants.CacheNames;
import com.dlc.shop.common.enums.StatusEnum;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.util.Arith;
import com.dlc.shop.common.util.DateUtils;
import com.dlc.shop.common.util.IpHelper;
import com.dlc.shop.common.util.RedisUtil;
import com.dlc.shop.dao.OrderItemMapper;
import com.dlc.shop.dao.ProdCommMapper;
import com.dlc.shop.dao.ProductMapper;
import com.dlc.shop.dao.ShopDetailMapper;
import com.dlc.shop.manager.impl.LangManager;
import com.dlc.shop.service.ProdCommService;
import com.dlc.shop.service.UserService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 商品评论
 *
 * @author xwc
 * @date 2019-04-19 10:43:57
 */
@Slf4j
@Service
@AllArgsConstructor
public class ProdCommServiceImpl extends ServiceImpl<ProdCommMapper, ProdComm> implements ProdCommService {

    private final ProdCommMapper prodCommMapper;
    private final RedissonClient redissonClient;
    private final OrderItemMapper orderItemMapper;
    private final ApplicationEventPublisher eventPublisher;
    private final UserService userService;
    private final ProductMapper productMapper;
    private final LangManager langManager;
    private final ShopDetailMapper shopDetailMapper;

    private final int MAX_CONTENT_LENGTH = 500;

    /**
     * 4分 5分为好评
     */
    private final List<Integer> GOOD_EVALUATE = Arrays.asList(4, 5);

     /**
     * 1分 2分为差评
     */
    private final List<Integer> BAD_EVALUATE = Arrays.asList(1, 2);

    /**
     * 3分为中评
     */
    private final List<Integer> MIDDLE_EVALUATE = Arrays.asList(3);




    @Override
    public ProdCommDataDto getProdCommDataByProdId(Long prodId) {
        ProdCommDataDto prodCommDataDto = prodCommMapper.getProdCommDataByProdId(prodId);
        //计算出好评率
        if (prodCommDataDto.getPraiseNumber() == 0 || prodCommDataDto.getNumber() == 0) {
            prodCommDataDto.setPositiveRating(0.0);
        } else {
            prodCommDataDto.setPositiveRating(Arith.div(Arith.mul(prodCommDataDto.getPraiseNumber(), 100), prodCommDataDto.getNumber(), 2));
        }
        return prodCommDataDto;
    }

    @Override
    public IPage<ProdCommDto> getProdCommDtoPageByUserId(Page page, String userId) {
        return prodCommMapper.getProdCommDtoPageByUserId(page, userId);
    }

    @Override
    public IPage<ProdCommDto> getProdCommDtoPageByProdId(Page page, Long prodId, Integer evaluate) {

        IPage<ProdCommDto> prodCommDtos = prodCommMapper.getProdCommDtoPageByProdId(page, prodId, evaluate);
        List<String> userIdList = prodCommDtos.getRecords().stream().map(ProdCommDto::getUserId).collect(Collectors.toList());
        List<User> userList;
        if (CollUtil.isNotEmpty(userIdList)) {
            userList = userService.list(new LambdaQueryWrapper<User>()
                    .in(User::getUserId, userIdList));
        } else {
            userList = new ArrayList<>();
        }
        Map<String, List<User>> userMap = userList.stream().collect(Collectors.groupingBy(User::getUserId));
        prodCommDtos.getRecords().forEach(prodCommDto -> {
            // 匿名评价
            if (prodCommDto.getIsAnonymous() == 1) {
                prodCommDto.setNickName(null);
                prodCommDto.setPic(null);
            }
            List<User> users = userMap.get(prodCommDto.getUserId());
            if (Objects.isNull(users) || users.size() == 0){
                prodCommDto.setIsWriteOff(true);
            }
        });
        return prodCommDtos;
    }

    @Override
    public IPage<ProdComm> getProdCommPage(Page page, ProdComm prodComm, Long shopId) {
        IPage<ProdComm> prodCommPage = prodCommMapper.getProdCommPage(page, prodComm, shopId);
        if (CollUtil.isEmpty(prodCommPage.getRecords())) {
            return prodCommPage;
        }
        List<Long> ids = prodCommPage.getRecords().stream().map(ProdComm::getProdId).filter(prodId -> Objects.nonNull(prodId)).collect(Collectors.toList());
        Map<Long, ProdLang> prodLangMap = langManager.getProdLangMap(ids);
        for (ProdComm item : prodCommPage.getRecords()) {
            ProdLang prodLang = prodLangMap.get(item.getProdId());
            if (Objects.nonNull(prodLang)) {
                item.setProdName(prodLang.getProdName());
            }
            if (Objects.equals(item.getIsAnonymous(), 1)) {
                item.setNickName(null);
                if (!Objects.isNull(item.getUser())) {
                    item.getUser().setNickName(null);
                }
            }
        }
        return prodCommPage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void comm(OrderItem orderItem, ProdCommParam prodCommParam) {
        long count = count(new LambdaQueryWrapper<ProdComm>().eq(ProdComm::getOrderItemId, orderItem.getOrderItemId()));
        // 已评论的商品，不需要再操作
        if (count > 0) {
            log.info("商品已评论， orderItemId: {}", orderItem.getOrderItemId());
            return;
        }
        // 插入评论
        ProdComm prodComm = this.buildProdComm(orderItem, prodCommParam);
        prodCommMapper.insert(prodComm);
        // 计算店铺评分
        Date endTime = new Date();
        Date startTime = DateUtils.getBeforeDay(endTime, -180);
        List<Integer> shopScoreList = prodCommMapper.listShopScore(startTime, orderItem.getShopId());
        shopScoreList.add(prodComm.getScore());
        Integer allScore = shopScoreList.stream().mapToInt(Integer::valueOf).sum();
        double shopScore = Arith.div(Double.parseDouble(allScore.toString()), Double.valueOf(String.valueOf(shopScoreList.size())));
        ShopDetailVO shopDetailVO = new ShopDetailVO();
        shopDetailVO.setShopId(orderItem.getShopId());
        shopDetailVO.setShopScore(shopScore);
        shopDetailMapper.batchUpdateShopScore(Collections.singletonList(shopDetailVO));
        // 清理一下店铺缓存
        RedisUtil.del(CacheNames.SHOP_DETAIL_ID_KEY + CacheNames.UNION + orderItem.getShopId());
        // 更新订单项评论状态
        orderItem.setCommSts(1);
        orderItemMapper.updateById(orderItem);
        // 判断商品是否已删除
        Product product = productMapper.selectById(prodComm.getProdId());
        if (Objects.equals(product.getStatus(), StatusEnum.DELETE.value())){
            return;
        }
        eventPublisher.publishEvent(new EsProductUpdateEvent(prodComm.getProdId(), null, EsOperationType.UPDATE_PROD_COMM));
    }

    @Override
    public void saveBatchComm(List<OrderItem> orderItems, List<ProdCommParam> prodCommParams) {
        if (CollectionUtils.isEmpty(orderItems)
                || CollectionUtils.isEmpty(prodCommParams)
                || !Objects.equals(orderItems.size(), prodCommParams.size())) {
            return;
        }
        // 数据准备
        List<Long> orderItemIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
        Set<Long> shopIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        Set<Long> prodIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        for (OrderItem orderItem: orderItems) {
            orderItemIds.add(orderItem.getOrderItemId());
            shopIds.add(orderItem.getShopId());
            prodIds.add(orderItem.getProdId());
        }
        // 批量插入评论
        List<ProdComm> prodComms = new ArrayList<>(orderItems.size());
        for (int i=0; i<orderItems.size(); i++) {
            prodComms.add(this.buildProdComm(orderItems.get(i), prodCommParams.get(i)));
        }
        this.saveBatch(prodComms);
        // 重新计算店铺评分(半年前评论)
        Date startTime = DateUtils.getBeforeDay(new Date(), -180);
        List<ProdComm> comms = this.list(new LambdaQueryWrapper<ProdComm>()
                .in(ProdComm::getShopId, shopIds)
                .ge(ProdComm::getRecTime, startTime));
        Map<Long, List<ProdComm>> shopCommMap = comms.stream().collect(Collectors.groupingBy(ProdComm::getShopId));
        List<ShopDetailVO> shopDetails = new ArrayList<>(shopCommMap.size());
        List<String> shopCacheNames = new ArrayList<>(shopCommMap.size());
        for (Map.Entry<Long, List<ProdComm>> entry : shopCommMap.entrySet()) {
            ShopDetailVO shopDetail = new ShopDetailVO();
            shopDetail.setShopId(entry.getKey());
            shopDetail.setShopScore(entry.getValue().stream().map(prodComm -> Double.valueOf(prodComm.getScore())).collect(Collectors.averagingDouble(Double::doubleValue)));
            shopDetails.add(shopDetail);
            shopCacheNames.add(CacheNames.SHOP_DETAIL_ID_KEY + CacheNames.UNION + entry.getKey());
        }
        shopDetailMapper.batchUpdateShopScore(shopDetails);
        // 清除店铺缓存
        RedisUtil.del(shopCacheNames);
        // 更新订单项状态为“已评论”
        orderItemMapper.update(new LambdaUpdateWrapper<OrderItem>()
                .set(OrderItem::getCommSts, 1)
                .in(OrderItem::getOrderItemId, orderItemIds));
        // 更新Es
        List<Product> products = productMapper.selectList(new LambdaQueryWrapper<Product>()
                .in(Product::getProdId, prodIds));
        List<Long> notDeleteProdIds = products.stream()
                .filter(product -> !Objects.equals(product.getStatus(), ProdStatusEnums.DELETE.getValue()))
                .map(Product::getProdId).toList();
        eventPublisher.publishEvent(new EsProductUpdateEvent(null, notDeleteProdIds, EsOperationType.UPDATE_BATCH));
    }

    @Override
    public void deleteByProdIds(Set<Long> prodIds) {
        if (CollectionUtil.isNotEmpty(prodIds)) {
            prodCommMapper.deleteByProdIds(prodIds);
        }
    }

    @Override
    public List<Long> getCommNumRankSpuIdsByShopIdAndTime(String key,
                                                          Long shopId,
                                                          Integer dayNum,
                                                          Integer expireTime,
                                                          Integer esRenovationSpuSort,
                                                          Long primaryCategoryId) {
        Date time = null;
        if(dayNum != 0) {
            time = DateUtil.offsetDay(new DateTime(), -dayNum);
        }

        List<Long> spuIds = RedisUtil.getListRange(key,0L,10L);
        if (CollectionUtil.isNotEmpty(spuIds)) {
            return spuIds;
        }

        // 加锁，防止缓存击穿
        RLock rLock = redissonClient.getLock("redisson_lock:sold_num_rank:" + key);
        try {
            int lockWait = 30;
            if (rLock.tryLock(lockWait, lockWait, TimeUnit.SECONDS)) {
                spuIds = RedisUtil.getListRange(key,0L,10L);
                if (CollectionUtil.isEmpty(spuIds)) {
                    spuIds = prodCommMapper.getCommNumRankSpuIdsByShopIdAndTime(shopId, time, esRenovationSpuSort, null, primaryCategoryId);
                    RedisUtil.setRightPushAll(key, spuIds, expireTime);
                    return spuIds;
                }
            } else {
                throw new YamiShopBindException("网络繁忙，请稍后再试");
            }
        } catch (InterruptedException e) {
            log.error("InterruptedException:", e);
        } finally {
            try {
                if (rLock.isLocked()) {
                    rLock.unlock();
                }
            } catch (Exception e) {
                log.error("Exception:", e);
            }
        }
        return spuIds;
    }

    @Override
    public List<ShopDetailVO> computeShopScore(Date startTime, Long shopId) {
        return prodCommMapper.computeShopScore(startTime, shopId);
    }

    @Override
    public Map<Long, Integer> listCommentNumByProdIds(List<Long> prodIds) {
        Map<Long, Integer> commNumMap = new HashMap<>();
        if (CollectionUtils.isEmpty(prodIds)) {
            return commNumMap;
        }
        List<ProdComm> prodComms = prodCommMapper.listCommentNumByProdIds(prodIds);
        if (CollectionUtils.isEmpty(prodComms)) {
            return commNumMap;
        }
        commNumMap = prodComms.stream().collect(Collectors.toMap(ProdComm::getProdId, ProdComm::getProdCommNum));
        return commNumMap;
    }

    private ProdComm buildProdComm(OrderItem orderItem, ProdCommParam prodCommParam) {
        ProdComm prodComm = new ProdComm();
        prodComm.setShopId(orderItem.getShopId());
        prodComm.setProdId(orderItem.getProdId());
        prodComm.setOrderItemId(orderItem.getOrderItemId());
        prodComm.setUserId(orderItem.getUserId());
        prodComm.setShopId(orderItem.getShopId());
        prodComm.setScore(prodCommParam.getScore());
        if (StrUtil.length(prodCommParam.getContent()) > MAX_CONTENT_LENGTH) {
            // 截取字符串
            prodCommParam.setContent(StrUtil.subWithLength(prodCommParam.getContent(), 0, MAX_CONTENT_LENGTH));
        }
        prodComm.setContent(prodCommParam.getContent());
        prodComm.setPics(StrUtil.isBlank(prodCommParam.getPics()) ? null : prodCommParam.getPics());
        prodComm.setIsAnonymous(prodCommParam.getIsAnonymous());
        prodComm.setRecTime(prodCommParam.getRecTime());
        // 平台不需要控评
        prodComm.setStatus(1);
        prodComm.setPostip(IpHelper.getIpAddr());
        // 好评
        if (GOOD_EVALUATE.contains(prodComm.getScore())) {
            prodComm.setEvaluate(0);
        }
        // 中评
        else if (MIDDLE_EVALUATE.contains(prodComm.getScore())) {
            prodComm.setEvaluate(1);
        }
        // 差评
        else if (BAD_EVALUATE.contains(prodComm.getScore())) {
            prodComm.setEvaluate(2);
        }
        return prodComm;
    }
}
